########################################################################## # Returns the value of distributions for a random value based on the inverse CDF ########################################################################## # Disclaimer: Although the generated values from these routines do not appear to be at least obviously # wrong and are using standard techniques, they are unchecked to ensure they are correct statistically. # We will be handling the following types of distributions # Normal distribution # Uniform distribution # Exponential distribution # Triangle distribution # Discrete distribution CC "Core" GET_ATTR_VAL objid:(nObjID) attrname:"Distribution Type" SET sDistTypeAttrString:(val) IF ((tokcnt(sDistTypeAttrString, "(")) > 2) { CC "AdoScript" ERRORBOX ("Error: Only one distribution type allowed at a time") EXIT } # Determine the distribution type SET sDistType:(copy(sDistTypeAttrString, 0, search(sDistTypeAttrString,"(", 0))) # Initialise a map to place all parameters in SET mParameters:(map()) IF (sDistType = "Normal") { # Parse the string to find the parameters SET fMuInit:(VAL replall(copy(sDistTypeAttrString, search(sDistTypeAttrString,"(", 0) + 1, search(sDistTypeAttrString,";", 0) - 1), ",", ".")) SET fSigmaInit:(VAL replall(copy(sDistTypeAttrString, search(sDistTypeAttrString,";", 0) + 2, search(sDistTypeAttrString,")", 0) - 1), ",", ".")) # Place these values in our parameters map SET mParameters["Normal"]:({fMuInit, fSigmaInit}) } ELSIF (sDistType = "Uniform") { # Parse the string to find the parameters SET fLowerUniformInit:(VAL replall(copy(sDistTypeAttrString, search(sDistTypeAttrString,"(", 0) + 1, search(sDistTypeAttrString,";", 0) - 1), ",", ".")) SET fUpperUniformInit:(VAL replall(copy(sDistTypeAttrString, search(sDistTypeAttrString,";", 0) + 2, search(sDistTypeAttrString,")", 0) - 1), ",", ".")) # Place these values in our parameters map SET mParameters["Uniform"]:({fUpperUniformInit, fLowerUniformInit}) } ELSIF (sDistType = "Exponential") { # Parse the string to find the parameters SET fExponentialExpectationInit:(VAL replall(copy(sDistTypeAttrString, search(sDistTypeAttrString,"(", 0) + 1, search(sDistTypeAttrString,")", 0) - 1), ",", ".")) # Place these values in our parameters map SET mParameters["Exponential"]:({fExponentialExpectationInit}) } ELSIF (sDistType = "Triangle") { # Parse parameters when the dialog box has the triangle option SET sDistTypeAttrString:(replall(sDistTypeAttrString, "Triangle", "")) SET sDistTypeAttrString:(replall(sDistTypeAttrString, "(", "")) SET sDistTypeAttrString:(replall(sDistTypeAttrString, " ", "")) SET sDistTypeAttrString:(replall(sDistTypeAttrString, ")", "")) SET nFirstPos:(search(sDistTypeAttrString,";", 0)) SET nSecondPos:(search(sDistTypeAttrString,";", nFirstPos + 1)) SET fLowerTriangleInit:(VAL copy(sDistTypeAttrString, 0, nFirstPos)) SET fMediumTriangleInit:(VAL copy(sDistTypeAttrString, nFirstPos + 1, nSecondPos - 1)) SET fUpperTriangleInit:(VAL copy(sDistTypeAttrString, nSecondPos + 1, LEN (sDistTypeAttrString))) # Place these values in our parameters map SET mParameters["Triangle"]:({fUpperTriangleInit, fMediumTriangleInit, fLowerTriangleInit}) } ELSIF (sDistType = "Discrete") { SET nNumberParams:(tokcnt(sDistTypeAttrString, ";")) # Actually, this is 1 less, but count from zero later SET sParamString:(replall(sDistTypeAttrString, "; ", ";")) SET sParamString:(replall(sParamString, "Discrete", "")) SET sParamString:(replall(sParamString, "(", "")) SET sParamString:(replall(sParamString, ")", "")) SET aVars:(array(0)) SET aProbs:(array(0)) FOR sPairString in:(sParamString) sep:";" { SET nSpacePos:(search(sPairString, " ", 0)) SET dummy:(aappend(aVars, copy(sPairString, 0, nSpacePos))) SET dummy:(aappend(aProbs, copy(sPairString, nSpacePos + 1, LEN(sPairString)))) } SET mParameters["Discrete_vars"]:(aVars) SET mParameters["Discrete_vals"]:(aProbs) } ELSE { CC "AdoScript" ERRORBOX ("Error: Distribution type not found/recognised") EXIT } # Initially get a random value in range [0,1) SET fRandomValInit:(random()) SET fPi:(3.14159265) # Get the random value based on the distribution type using a procedure GET_DIST_VAL sDistribType:(sDistType) mParamMap:(mParameters) fRandomVal:(fRandomValInit) fResult:fResult # We write the random value to the attribute "Random Value". Note: Since we have to allow for the case of distribution of type "Discrete" # this attribute is a string. CC "Core" SET_ATTR_VAL objid:(nObjID) attrname:"Random Value" val:(fResult) # Procedure to produce the value of a distribution for a given random value PROCEDURE GET_DIST_VAL sDistribType:string mParamMap:map fRandomVal:real fResult:reference { IF (sDistribType = "Normal") { SET fMu:((mParamMap["Normal"])[0]) SET fSigma:((mParamMap["Normal"])[1]) SET fResult:((1/(2 * fSigma * sqrt(fPi))) * (exp(-(((fRandomVal - fMu) * (fRandomVal - fMu))/(2 * fSigma * fSigma))))) } IF (sDistribType = "Exponential") { SET fLambda:((mParamMap["Exponential"])[0]) SET fResult:(-(1/fLambda) * (log(1 - fRandomVal))) } IF (sDistribType = "Uniform") { SET fUpperUniform:((mParamMap["Uniform"])[0]) SET fLowerUniform:((mParamMap["Uniform"])[1]) IF (fUpperUniform = fLowerUniform) { CC "AdoScript" ERRORBOX ("Error: Uniform distribution undefined on zero length interval (Lower bound = Upper bound)") EXIT } ELSIF (fUpperUniform < fLowerUniform) { CC "AdoScript" ERRORBOX ("Error: Upper bound < Lower bound") EXIT } ELSE { SET fResult:(fLowerUniform + fRandomVal * (fUpperUniform - fLowerUniform)) } } IF (sDistribType = "Triangle") { SET fUpperTriangle:((mParamMap["Triangle"])[0]) SET fMediumTriangle:((mParamMap["Triangle"])[1]) SET fLowerTriangle:((mParamMap["Triangle"])[2]) # There is a problem with the inverse CDF if the random value is exactly one or zero IF ((fRandomVal = 1) OR (fRandomVal = 0)) { CC "AdoScript" ERRORBOX ("Error: Exception occurred, please try again") EXIT } IF ((fUpperTriangle < fLowerTriangle) OR (fUpperTriangle < fMediumTriangle) OR (fMediumTriangle < fLowerTriangle)) { CC "AdoScript" ERRORBOX ("Error: Parameters need to be Lower < Medium < Upper") EXIT } ELSE { SET fc:((fMediumTriangle - fLowerTriangle)/(fUpperTriangle - fLowerTriangle)) IF ((fRandomVal < fc) AND (fRandomVal > 0)) { SET fResult:(fLowerTriangle + sqrt(fRandomVal * (fUpperTriangle - fLowerTriangle) * (fMediumTriangle - fLowerTriangle))) } ELSE ((fc <= fRandomVal) AND (fRandomVal < 1)) { SET fResult:(fUpperTriangle - sqrt((1 - fRandomVal) * (fUpperTriangle - fLowerTriangle) * (fUpperTriangle - fMediumTriangle))) } } } IF (sDistribType = "Discrete") { SET aProbs:(mParamMap["Discrete_vals"]) SET aVariables:(mParamMap["Discrete_vars"]) SET nNumProbs:(LEN aProbs) # This loop orders the prob vals in increasing order, and arranges the variables # in the corresponding order FOR i from:(0) to:(nNumProbs - 1) { FOR j from:(i) to:(nNumProbs - 1) { SET fCurrentProb:(aProbs[i]) SET fCompProb:(aProbs[j]) SET sCurrentVar:(aVariables[i]) SET sCompVar:(aVariables[j]) IF (fCompProb <= fCurrentProb) { SET dummy: (areplace(aProbs, i, fCompProb)) SET dummy: (areplace(aProbs, j, fCurrentProb)) SET dummy: (areplace(aVariables, i, sCompVar)) SET dummy: (areplace(aVariables, j, sCurrentVar)) } } } # Next we cumulate the probabilities, which will then allow us to choose one of them # according to the random variable SET aCumProbs:(array(nNumProbs)) SET fCumulator:(VAL aProbs[0]) SET aCumProbs[0]:(fCumulator) FOR i from:(1) to:(nNumProbs - 1) { SET fCumulator:(fCumulator + (VAL aProbs[i])) SET aCumProbs[i]:(fCumulator) } IF (fRandomVal < aCumProbs[0]) { SET fResult:(aVariables[0]) } ELSE { FOR j from:(0) to:(nNumProbs - 2) { IF ((aCumProbs[j] <= fRandomVal) AND (fRandomVal < aCumProbs[j+1])) { SET fResult:(aVariables[j+1]) } } } } # We use the Box-Muller transform to get the value for the Gaussian (Normal) case IF (sDistribType = "Normal") { # First load the parameters SET fMu:((mParamMap["Normal"])[0]) SET fSigma:((mParamMap["Normal"])[1]) # We need two random numbers in [0,1) for this SET fRanVal1:(fRandomVal) SET fRanVal2:(random()) # This gives a Normal distributed rand number with mean 0 and std. dev. 1 SET fResult:(sqrt(-2 * log(fRanVal1)) * cos(2 * fPi * fRanVal2)) # We now add the mean and multiply by the std. dev. accordingly SET fResult:(fMu + fSigma * fResult) } }